package de.mmis.core.base;

import java.io.UnsupportedEncodingException;
import java.lang.reflect.Proxy;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class Toolbox {

	/**
	 * Utility function for converting a single String into a List<String>
	 * 
	 * @param s
	 *            single string
	 * @return a List<String> containing one String
	 */
	public static <T> List<T> toList(T s) {
		List<T> ret = new ArrayList<T>();
		ret.add(s);
		return ret;
	}

	/**
	 * Utility function for converting a String to its UTF-8 byte representation
	 * 
	 * @param string
	 *            string to convert
	 * @return byte array containing UTF-8 bytes of given string
	 */
	public static byte[] toUTF8Bytes(String string) {
		try {
			return string.getBytes("UTF-8");
		} catch (UnsupportedEncodingException e) {
			// Exception cannot occur, because by definition
			// it is contained in any JDK
			throw new RuntimeException(
					"Something went really wrong, because this VM does not know the UTF-8 encoding",
					e);
		}
	}

	/**
	 * Utility function for converting a byte array to a String using UTF-8
	 * encoding
	 * 
	 * @param data
	 *            byte array to convert
	 * @return string containing UTF-8 decoded data
	 */
	public static String toUTF8String(byte[] data) {
		try {
			return new String(data, "UTF-8");
		} catch (UnsupportedEncodingException e) {
			// Exception cannot occur, because by definition
			// it is contained in any JDK
			throw new RuntimeException(
					"Something went really wrong, because this VM does not know the UTF-8 encoding",
					e);
		}
	}

	/**
	 * generate list of all interfaces a specific class implements
	 * 
	 * @param clazz
	 *            class to find interfaces for
	 * @param interfaceList
	 *            list the found interfaces are appended to. if set to
	 *            <code>null</code>, a new ArrayList will be created.
	 * @return the list provided with parameter interfaceList. Will never be
	 *         <code>null</code>.
	 */
	public static List<Class<?>> getAllInterfaces(Class<?> clazz,
			List<Class<?>> interfaceList) {
		List<Class<?>> returnedInterfaceList = interfaceList;
		if (returnedInterfaceList == null)
			returnedInterfaceList = new ArrayList<Class<?>>();
		if (clazz == null)
			return returnedInterfaceList;
		if (clazz.isInterface() && !returnedInterfaceList.contains(clazz))
			returnedInterfaceList.add(clazz);
		for (Class<?> c : clazz.getInterfaces())
			getAllInterfaces(c, returnedInterfaceList);
		getAllInterfaces(clazz.getSuperclass(), returnedInterfaceList);
		return returnedInterfaceList;
	}

	/**
	 * auxiliary method that tries to find a ClassLoader that can load all the
	 * classes. Used to find correct ClassLoader for {@link Proxy} creation
	 * 
	 * @param classes
	 * @return a {@link ClassLoader} all given classes are visible from, or
	 *         null, if none can be found.
	 */
	public static ClassLoader findCommonClassLoader(Class<?>[] classes) {
		clloop: for (Class<?> clclass : classes) {
			if (clclass.getClassLoader() == null)
				continue clloop;
			try {
				for (Class<?> c : classes)
					if (clclass.getClassLoader().loadClass(c.getName()) != c)
						continue clloop;
			} catch (ClassNotFoundException e) {
				continue clloop;
			}
			return clclass.getClassLoader();
		}
		return null;
	}

	/**
	 * testing whether two classes are equal, identifying the primitive types
	 * with their boxed version
	 * 
	 * @param c1
	 *            first class
	 * @param c2
	 *            second class
	 * @return <code>true</code>, if the given classes are equal, otherwise
	 *         <code>false</code>
	 */
	public static boolean classEquals(Class<?> c1, Class<?> c2) {
		if (!c1.isPrimitive() && c2.isPrimitive())
			return classEquals(c2, c1);
		if (c1 == c2)
			return true;
		if (c1 == byte.class && c2 == Byte.class)
			return true;
		if (c1 == short.class && c2 == Short.class)
			return true;
		if (c1 == int.class && c2 == Integer.class)
			return true;
		if (c1 == long.class && c2 == Long.class)
			return true;
		if (c1 == char.class && c2 == Character.class)
			return true;
		if (c1 == float.class && c2 == Float.class)
			return true;
		if (c1 == double.class && c2 == Double.class)
			return true;
		if (c1 == boolean.class && c2 == Boolean.class)
			return true;
		return false;
	}

	/**
	 * alternative version of Class.forName that can handle primitive types as
	 * well
	 * 
	 * @param name
	 *            fully qualified name of the class to load
	 * @param classLoader
	 *            {@link ClassLoader} that loads the class. If set to
	 *            <code>null</code>, Class.forName will be used
	 * @return the loaded class object
	 * @throws ClassNotFoundException
	 *             the class cannot be found in given class loader
	 */
	public static Class<?> forName(String name, ClassLoader classLoader)
			throws ClassNotFoundException {
		if (builtInClassMap.containsKey(name))
			return builtInClassMap.get(name);
		return classLoader == null ? Class.forName(name) : Class.forName(name,
				false, classLoader);
	}

	// map of primitive data types
	private static final Map<String, Class<?>> builtInClassMap = new HashMap<String, Class<?>>();
	static {
		builtInClassMap.put("int", Integer.TYPE);
		builtInClassMap.put("long", Long.TYPE);
		builtInClassMap.put("double", Double.TYPE);
		builtInClassMap.put("float", Float.TYPE);
		builtInClassMap.put("bool", Boolean.TYPE);
		builtInClassMap.put("char", Character.TYPE);
		builtInClassMap.put("byte", Byte.TYPE);
		builtInClassMap.put("void", Void.TYPE);
		builtInClassMap.put("short", Short.TYPE);
	}

}
